// Copyright (C) 2018-2020 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

/**
 * @brief This is a header file with common inference engine definitions.
 *
 * @file ie_common.h
 */
#pragma once

#include <algorithm>
#include <cstdlib>
#include <memory>
#include <ostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <stdexcept>
#include <iterator>

#include <ie_api.h>
#ifndef NDEBUG
#include <cassert>
#endif
namespace InferenceEngine {
/**
 * @brief Represents tensor size.
 *
 * The order is opposite to the order in Caffe*: (w,h,n,b) where the most frequently changing element in memory is
 * first.
 */
using SizeVector = std::vector<size_t>;

/**
 * @brief The main data representation node
 */
class Data;

/**
 * @brief Smart pointer to Data
 */
using DataPtr = std::shared_ptr<Data>;

/**
 * @brief Smart pointer to constant Data
 */
using CDataPtr = std::shared_ptr<const Data>;

/**
 * @brief Smart weak pointer to Data
 */
using DataWeakPtr = std::weak_ptr<Data>;

/**
 * @union UserValue
 * @brief The method holds the user values to enable binding of data per graph node.
 */
union UserValue {
    int v_int;  //!< An integer value
    float v_float;  //!< A floating point value
    void* v_ptr;  //!< A pointer to a void
};

/**
 * @enum Layout
 * @brief Layouts that the inference engine supports
 */
enum Layout : uint8_t {
    ANY = 0,  //!< "any" layout

    // I/O data layouts
    NCHW = 1,  //!< NCHW layout for input / output blobs
    NHWC = 2,  //!< NHWC layout for input / output blobs
    NCDHW = 3,  //!< NCDHW layout for input / output blobs
    NDHWC = 4,  //!< NDHWC layout for input / output blobs

    // weight layouts
    OIHW = 64,  //!< NDHWC layout for operation weights
    GOIHW = 65,  //!< NDHWC layout for operation weights
    OIDHW = 66,  //!< NDHWC layout for operation weights
    GOIDHW = 67,  //!< NDHWC layout for operation weights

    // Scalar
    SCALAR = 95,  //!< A scalar layout

    // bias layouts
    C = 96,  //!< A bias layout for operation

    // Single image layouts
    CHW = 128,  //!< A single image layout (e.g. for mean image)
    HWC = 129,  //!< A single image layout (e.g. for mean image)

    // 2D
    HW = 192,  //!< HW 2D layout
    NC = 193,  //!< HC 2D layout
    CN = 194,  //!< CN 2D layout

    BLOCKED = 200,  //!< A blocked layout
};

/**
 * @brief Prints a string representation of InferenceEngine::Layout to a stream
 * @param out An output stream to send to
 * @param p A layout value to print to a stream
 * @return A reference to the `out` stream
 */
inline std::ostream& operator<<(std::ostream& out, const Layout& p) {
    switch (p) {
#define PRINT_LAYOUT(name) \
    case name:             \
        out << #name;      \
        break;

        PRINT_LAYOUT(ANY);
        PRINT_LAYOUT(NCHW);
        PRINT_LAYOUT(NHWC);
        PRINT_LAYOUT(NCDHW);
        PRINT_LAYOUT(NDHWC);
        PRINT_LAYOUT(OIHW);
        PRINT_LAYOUT(C);
        PRINT_LAYOUT(CHW);
        PRINT_LAYOUT(HWC);
        PRINT_LAYOUT(HW);
        PRINT_LAYOUT(NC);
        PRINT_LAYOUT(CN);
        PRINT_LAYOUT(BLOCKED);
#undef PRINT_LAYOUT
    default:
        out << static_cast<int>(p);
        break;
    }
    return out;
}

/**
 * @enum ColorFormat
 * @brief Extra information about input color format for preprocessing
 */
enum ColorFormat : uint32_t {
    RAW = 0u,  ///< Plain blob (default), no extra color processing required
    RGB,       ///< RGB color format
    BGR,       ///< BGR color format, default in DLDT
    RGBX,      ///< RGBX color format with X ignored during inference
    BGRX,      ///< BGRX color format with X ignored during inference
    NV12,      ///< NV12 color format represented as compound Y+UV blob
    I420,      ///< I420 color format represented as compound Y+U+V blob
};

/**
 * @brief Prints a string representation of InferenceEngine::ColorFormat to a stream
 * @param out An output stream to send to
 * @param fmt A color format value to print to a stream
 * @return A reference to the `out` stream
 */
inline std::ostream& operator<<(std::ostream& out, const ColorFormat& fmt) {
    switch (fmt) {
#define PRINT_COLOR_FORMAT(name) \
    case name:                   \
        out << #name;            \
        break;

        PRINT_COLOR_FORMAT(RAW);
        PRINT_COLOR_FORMAT(RGB);
        PRINT_COLOR_FORMAT(BGR);
        PRINT_COLOR_FORMAT(RGBX);
        PRINT_COLOR_FORMAT(BGRX);
        PRINT_COLOR_FORMAT(NV12);
        PRINT_COLOR_FORMAT(I420);
#undef PRINT_COLOR_FORMAT

    default:
        out << static_cast<uint32_t>(fmt);
        break;
    }
    return out;
}

/**
 * @struct InferenceEngineProfileInfo
 * @brief Represents basic inference profiling information per layer.
 *
 * If the layer is executed using tiling, the sum time per each tile is indicated as the total execution time.
 * Due to parallel execution, the total execution time for all layers might be greater than the total inference time.
 */
struct InferenceEngineProfileInfo {
    /**
     * @brief Defines the general status of the layer
     */
    enum LayerStatus {
        NOT_RUN,  //!< A layer is not executed
        OPTIMIZED_OUT,  //!< A layer is optimized out during graph optimization phase
        EXECUTED  //!< A layer is executed
    };

    /**
     * @brief Defines a layer status
     */
    LayerStatus status;

    /**
     * @brief The absolute time in microseconds that the layer ran (in total)
     */
    long long realTime_uSec;
    /**
     * @brief The net host cpu time that the layer ran
     */
    long long cpu_uSec;

    /**
     * @brief An execution type of unit
     */
    char exec_type[256] = {};

    /**
     * @brief A layer type
     */
    char layer_type[256] = {};

    /**
     * @brief An execution index of the unit
     */
    unsigned execution_index;
};

/**
 * @enum StatusCode
 * @brief This enum contains codes for all possible return values of the interface functions
 */
enum StatusCode : int {
    OK = 0,
    GENERAL_ERROR = -1,
    NOT_IMPLEMENTED = -2,
    NETWORK_NOT_LOADED = -3,
    PARAMETER_MISMATCH = -4,
    NOT_FOUND = -5,
    OUT_OF_BOUNDS = -6,
    /*
     * @brief exception not of std::exception derived type was thrown
     */
    UNEXPECTED = -7,
    REQUEST_BUSY = -8,
    RESULT_NOT_READY = -9,
    NOT_ALLOCATED = -10,
    INFER_NOT_STARTED = -11,
    NETWORK_NOT_READ = -12,
    INFER_CANCELLED = -13
};

/**
 * @struct ResponseDesc
 * @brief  Represents detailed information for an error
 */
struct ResponseDesc {
    /**
     * @brief A character buffer that holds the detailed information for an error.
     */
    char msg[4096] = {};
};

/**
 * @brief Response structure encapsulating information about supported layer
 */
struct QueryNetworkResult {
    /**
     * @brief A map of supported layers:
     * - key - a layer name
     * - value - a device name on which layer is assigned
     */
    std::map<std::string, std::string> supportedLayersMap;

    /**
     * @brief A status code
     */
    StatusCode rc = OK;

    /**
     * @brief Response message
     */
    ResponseDesc resp;
};

namespace details {
struct INFERENCE_ENGINE_DEPRECATED("Use InferRequest::Exception")
INFERENCE_ENGINE_API_CLASS(InferenceEngineException) : public std::runtime_error {
    using std::runtime_error::runtime_error;
    bool hasStatus() const {return true;}
    StatusCode getStatus() const;
};
}  // namespace details

/**
 * @brief Base Inference Engine exception class
 */
IE_SUPPRESS_DEPRECATED_START
struct INFERENCE_ENGINE_API_CLASS(Exception) : public details::InferenceEngineException {
    using InferenceEngineException::InferenceEngineException;
};
IE_SUPPRESS_DEPRECATED_END

/// @cond
namespace details {
    template<typename ExceptionType> struct ExceptionTraits;
}

#define INFERENCE_ENGINE_DECLARE_EXCEPTION(ExceptionType, statusCode)                           \
struct INFERENCE_ENGINE_API_CLASS(ExceptionType) final : public InferenceEngine::Exception {    \
    using Exception::Exception;                                                                 \
};                                                                                              \
namespace details {                                                                             \
template<> struct ExceptionTraits<ExceptionType> {                                              \
    static const char* string() {return "[ " #statusCode " ]";}                                 \
};                                                                                              \
}
/// @endcond

/** @brief This class represents StatusCode::GENERAL_ERROR exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(GeneralError, GENERAL_ERROR)

/** @brief This class represents StatusCode::NOT_IMPLEMENTED exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(NotImplemented, NOT_IMPLEMENTED)

/** @brief This class represents StatusCode::NETWORK_NOT_LOADED exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(NetworkNotLoaded, NETWORK_NOT_LOADED)

/** @brief This class represents StatusCode::PARAMETER_MISMATCH exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(ParameterMismatch, PARAMETER_MISMATCH)

/** @brief This class represents StatusCode::NOT_FOUND exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(NotFound, NOT_FOUND)

/** @brief This class represents StatusCode::OUT_OF_BOUNDS exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(OutOfBounds, OUT_OF_BOUNDS)

/** @brief This class represents StatusCode::UNEXPECTED exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(Unexpected, UNEXPECTED)

/** @brief This class represents StatusCode::REQUEST_BUSY exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(RequestBusy, REQUEST_BUSY)

/** @brief This class represents StatusCode::RESULT_NOT_READY exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(ResultNotReady, RESULT_NOT_READY)

/** @brief This class represents StatusCode::NOT_ALLOCATED exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(NotAllocated, NOT_ALLOCATED)

/** @brief This class represents StatusCode::INFER_NOT_STARTED exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(InferNotStarted, INFER_NOT_STARTED)

/** @brief This class represents StatusCode::NETWORK_NOT_READ exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(NetworkNotRead, NETWORK_NOT_READ)

/** @brief This class represents StatusCode::INFER_CANCELLED exception */
INFERENCE_ENGINE_DECLARE_EXCEPTION(InferCancelled, INFER_CANCELLED)

/**
 * @private
 */
#undef INFERENCE_ENGINE_DECLARE_EXCEPTION

namespace details {
/**
 * @brief Tag struct used to throw exception
 */
template<typename ExceptionType>
struct ThrowNow final {
    [[noreturn]] void operator<<=(const std::ostream& ostream) {
        std::ostringstream stream;
        stream << ostream.rdbuf();
        throw ExceptionType{stream.str()};
    }
};

/// @cond
#ifndef NDEBUG
#define IE_LOCATION '\n' << __FILE__  << ':' << __LINE__<< ' '
#else
#define IE_LOCATION ""
#endif  // NDEBUG
/// @endcond

/**
 * @def IE_THROW
 * @brief A macro used to throw specefied exception with a description
 */
#define IE_THROW(ExceptionType)     \
    InferenceEngine::details::ThrowNow<InferenceEngine::ExceptionType>{} <<= std::stringstream{} << IE_LOCATION

/**
 * @def THROW_IE_EXCEPTION
 * @brief A macro used to throw general exception with a description
 */
#define THROW_IE_EXCEPTION IE_THROW(GeneralError)

/**
 * @def THROW_IE_EXCEPTION_WITH_STATUS
 * @brief A macro used to throw general exception with a description and status
 */
#define THROW_IE_EXCEPTION_WITH_STATUS(ExceptionType)                              \
    IE_THROW(ExceptionType) << InferenceEngine::details::ExceptionTraits<InferenceEngine::ExceptionType>::string() << ' '

/**
 * @def IE_ASSERT
 * @brief Uses assert() function if NDEBUG is not defined, InferenceEngine exception otherwise
 */
#ifdef NDEBUG
#define IE_ASSERT(EXPRESSION)                                               \
    if (!(EXPRESSION))                                                      \
    IE_THROW(GeneralError) << " AssertionFailed: " << #EXPRESSION  // NOLINT
#else
/**
 * @private
 */
struct NullStream {
    template <typename T>
    NullStream& operator<<(const T&) noexcept {return *this;}
};

#define IE_ASSERT(EXPRESSION) \
    assert((EXPRESSION));     \
    InferenceEngine::details::NullStream()
#endif  // NDEBUG

/// @cond
#define IE_EXCEPTION_CASE(TYPE_ALIAS, STATUS_CODE, EXCEPTION_TYPE, ...)                         \
    case InferenceEngine::STATUS_CODE : {                                                       \
        using InferenceEngine::EXCEPTION_TYPE; using TYPE_ALIAS = EXCEPTION_TYPE;  __VA_ARGS__; \
    } break;
/// @endcond

/**
 * @def IE_EXCEPTION_SWITCH
 * @brief Generate Switch statement over error codes adn maps them to coresponding exceptions type
 */
#define IE_EXCEPTION_SWITCH(STATUS, TYPE_ALIAS, ...)                                            \
    switch (STATUS) {                                                                           \
        IE_EXCEPTION_CASE(TYPE_ALIAS, GENERAL_ERROR      , GeneralError      , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, NOT_IMPLEMENTED    , NotImplemented    , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, NETWORK_NOT_LOADED , NetworkNotLoaded  , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, PARAMETER_MISMATCH , ParameterMismatch , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, NOT_FOUND          , NotFound          , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, OUT_OF_BOUNDS      , OutOfBounds       , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, UNEXPECTED         , Unexpected        , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, REQUEST_BUSY       , RequestBusy       , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, RESULT_NOT_READY   , ResultNotReady    , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, NOT_ALLOCATED      , NotAllocated      , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, INFER_NOT_STARTED  , InferNotStarted   , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, NETWORK_NOT_READ   , NetworkNotRead    , __VA_ARGS__)     \
        IE_EXCEPTION_CASE(TYPE_ALIAS, INFER_CANCELLED    , InferCancelled    , __VA_ARGS__)     \
        default: IE_ASSERT(!"Unreachable");                                                     \
    }

/**
 * @private
 */
#define CALL_STATUS_FNC(function, ...)                                                          \
    if (!actual) THROW_IE_EXCEPTION << "Wrapper used was not initialized.";                     \
    ResponseDesc resp;                                                                          \
    auto res = actual->function(__VA_ARGS__, &resp);                                            \
    if (res != OK) IE_EXCEPTION_SWITCH(res, ExceptionType,                                      \
            InferenceEngine::details::ThrowNow<ExceptionType>{}                                 \
                <<= std::stringstream{} << IE_LOCATION << resp.msg)

/**
 * @private
 */
#define CALL_STATUS_FNC_NO_ARGS(function)                                                                   \
    if (!actual)  THROW_IE_EXCEPTION << "Wrapper used in the CALL_STATUS_FNC_NO_ARGS was not initialized."; \
    ResponseDesc resp;                                                                                      \
    auto res = actual->function(&resp);                                                                     \
    if (res != OK) IE_EXCEPTION_SWITCH(res, ExceptionType,                                                  \
            InferenceEngine::details::ThrowNow<ExceptionType>{}                                             \
                <<= std::stringstream{} << IE_LOCATION)

/**
 * @private
 */
#define CALL_FNC_NO_ARGS(function)         \
    if (!actual) THROW_IE_EXCEPTION << "Wrapper used in the CALL_FNC_NO_ARGS was not initialized."; \
    ResponseDesc resp;                     \
    auto result = actual->function(&resp); \
    if (resp.msg[0] != '\0') {             \
         THROW_IE_EXCEPTION << resp.msg    \
    }                                      \
    return result;
}  // namespace details
}  // namespace InferenceEngine
#if defined(_WIN32)
#define __PRETTY_FUNCTION__ __FUNCSIG__
#else
#define __PRETTY_FUNCTION__ __PRETTY_FUNCTION__
#endif
